package com.linkgie.galaxyframework.utils;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

public class TimeUtils {

	private static SimpleDateFormat STANDARD_FULL_FORMAT = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss.SSS");

	private static SimpleDateFormat STANDARD_FORMAT = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss");

	private static final TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC");

	/**
	 * 1 天的毫秒总数；
	 */
	private static final long ONE_DAY_MILLIS = 24 * 3600 * 1000;

	/**
	 * 获取若干天之后的时间值；
	 * <p>
	 * 如果参数为 0 ，则返回当前时间；
	 * 
	 * <p>
	 * 如果参数为负数，则返回若干天之前的时间；
	 * 
	 * @param days
	 * @return
	 */
	@Deprecated
	public static long getTimeMillisAfterDay(int days) {
		return timeMillisAfterDays(System.currentTimeMillis(), days);
	}

	/**
	 * 获取从当前时间开始，经过指定天数之后的时刻；
	 * <p>
	 * 如果参数为 0 ，则返回当前时间；
	 * 
	 * <p>
	 * 如果参数为负数，则返回若干天之前的时间；
	 * 
	 * @param days 天数；
	 * @return
	 */
	public static long timeMillisAfterDays(int days) {
		return timeMillisAfterDays(System.currentTimeMillis(), days);
	}

	/**
	 * 返回从指定时刻开始，经过指定天数之后的时刻；
	 * 
	 * @param startMillis 开始时刻，单位毫秒值；
	 * @param days        天数；
	 * @return
	 */
	public static long timeMillisAfterDays(long startMillis, int days) {
		return startMillis + ONE_DAY_MILLIS * days;
	}

	/**
	 * 返回指定日期的起始时间刻度值；
	 * <p>
	 * 以当前系统默认时区定义；
	 * 
	 * @param year       年；
	 * @param month      月；从 0 开始，最大值为 11 ; 0 表示 1 月，11 表示12月；
	 * @param dayOfMonth 日；
	 * @param millis     毫秒；
	 * @return
	 */
	public static long timeMillisOfDate(int year, int month, int dayOfMonth) {
		return timeMillisOfDate(year, month, dayOfMonth, 0, 0, 0, 0, TimeZone.getDefault());
	}

	/**
	 * 返回指定日期的起始时间刻度值；
	 * <p>
	 * 以 UTC 时区定义；
	 * 
	 * @param year       年；
	 * @param month      月；从 0 开始，最大值为 11 ; 0 表示 1 月，11 表示12月；
	 * @param dayOfMonth 日；
	 * @param millis     毫秒；
	 * @param timeZone   时区；
	 * @return
	 */
	public static long utcTimeMillisOfDate(int year, int month, int dayOfMonth) {
		return timeMillisOfDate(year, month, dayOfMonth, 0, 0, 0, 0, UTC_TIMEZONE);
	}

	/**
	 * 返回指定日期的起始时间刻度值；
	 * 
	 * @param year       年；
	 * @param month      月；从 0 开始，最大值为 11 ; 0 表示 1 月，11 表示12月；
	 * @param dayOfMonth 日；
	 * @param millis     毫秒；
	 * @param timeZone   时区；
	 * @return
	 */
	public static long timeMillisOfDate(int year, int month, int dayOfMonth, TimeZone timeZone) {
		return timeMillisOfDate(year, month, dayOfMonth, 0, 0, 0, 0, timeZone);
	}

	/**
	 * 返回指定日期时间的毫秒时间刻度值；
	 * <p>
	 * 以 UTC 时区定义；
	 * 
	 * @param year       年；
	 * @param month      月；从 0 开始，最大值为 11 ; 0 表示 1 月，11 表示12月；
	 * @param dayOfMonth 日；
	 * @param hours      时；
	 * @param minutes    分；
	 * @param seconds    秒；
	 * @param millis     毫秒；
	 * @return
	 */
	public static long utcTimeMillisOfDate(int year, int month, int dayOfMonth, int hours, int minutes, int seconds,
			int millis) {
		return timeMillisOfDate(year, month, dayOfMonth, hours, minutes, seconds, millis, UTC_TIMEZONE);
	}

	/**
	 * 返回指定日期时间的毫秒时间刻度值；
	 * 
	 * @param year       年；
	 * @param month      月；从 0 开始，最大值为 11 ; 0 表示 1 月，11 表示12月；
	 * @param dayOfMonth 日；
	 * @param hours      时；
	 * @param minutes    分；
	 * @param seconds    秒；
	 * @param millis     毫秒；
	 * @param timeZone   时区；
	 * @return
	 */
	public static long timeMillisOfDate(int year, int month, int dayOfMonth, int hours, int minutes, int seconds,
			int millis) {
		return timeMillisOfDate(year, month, dayOfMonth, hours, minutes, seconds, millis, TimeZone.getDefault());
	}

	/**
	 * 返回指定日期时间的毫秒时间刻度值；
	 * 
	 * @param year       年；
	 * @param month      月；从 0 开始，最大值为 11 ; 0 表示 1 月，11 表示12月；
	 * @param dayOfMonth 日；
	 * @param hours      时；
	 * @param minutes    分；
	 * @param seconds    秒；
	 * @param millis     毫秒；
	 * @param timeZone   时区；
	 * @return
	 */
	public static long timeMillisOfDate(int year, int month, int dayOfMonth, int hours, int minutes, int seconds,
			int millis, TimeZone timeZone) {
		Calendar calendar = getCalendar(year, month, dayOfMonth, hours, minutes, seconds, millis, timeZone);
		return calendar.getTimeInMillis();
	}

	private static Calendar getCalendar(int year, int month, int dayOfMonth) {
		return getCalendar(year, month, dayOfMonth, 0, 0, 0, 0, TimeZone.getDefault());
	}

	private static Calendar getCalendar(int year, int month, int dayOfMonth, TimeZone timeZone) {
		return getCalendar(year, month, dayOfMonth, 0, 0, 0, 0, timeZone);
	}

	private static Calendar getCalendar(int year, int month, int dayOfMonth, int hours, int minutes, int seconds,
			int millis, TimeZone timeZone) {
		Calendar calendar = Calendar.getInstance(timeZone);
		calendar.set(year, month, dayOfMonth, hours, minutes, seconds);
		calendar.set(Calendar.MILLISECOND, millis);
		return calendar;
	}

	private static Calendar getCurrentYearCalendar(int month, int dayOfMonth, int hours, int minutes, int seconds,
			int millis, TimeZone timeZone) {
		Calendar calendar = Calendar.getInstance(timeZone);
		calendar.set(Calendar.MONTH, month);
		calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
		calendar.set(Calendar.HOUR_OF_DAY, hours);
		calendar.set(Calendar.MINUTE, minutes);
		calendar.set(Calendar.SECOND, seconds);
		calendar.set(Calendar.MILLISECOND, millis);
		return calendar;
	}

	/**
	 * UTC 时间的当天午夜 0 点时刻；
	 * <p>
	 * 单位：毫秒刻度；
	 * 
	 * @return
	 */
	public static long utcTodayStartMillis() {
		long now = System.currentTimeMillis();

		// 整数天为单位的时间刻度；
		// 以此抹掉 1 天以内的时间刻度；
		long nowDays = now / ONE_DAY_MILLIS;

		// 把整数天为单位的时间刻度，转为毫秒为单位的时间刻度；
		return nowDays * ONE_DAY_MILLIS;
	}

	/**
	 * 当前时区的当天午夜 0 点时刻；
	 * <p>
	 * 单位：毫秒刻度；
	 * 
	 * @return
	 */
	public static long todayStartMillis() {
		Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);

		return calendar.getTimeInMillis();
	}

	/**
	 * 当前时区的本周周一午夜 0 点时刻；
	 * <p>
	 * 单位：毫秒刻度；
	 * 
	 * @return
	 */
	public static long mondayStartMillis() {
		return dayOfWeekStartMillis(1);
	}

	/**
	 * 当前时区的下周周一午夜 0 点时刻；
	 * <p>
	 * 单位：毫秒刻度；
	 * 
	 * @return
	 */
	public static long nextMondayStartMillis() {
		long thisMondayStartMillis = dayOfWeekStartMillis(1);
		return timeMillisAfterDays(thisMondayStartMillis, 7);
	}

	/**
	 * 当前时区的本周指定日期的午夜 0 点时刻；
	 * <p>
	 * 单位：毫秒刻度；
	 * 
	 * @param dayOfWeek 表示星期几的数值；按照 ISO 标准，每周从周一开始，1 表示周一，2 表示周二，以此类推，7 表示周日；
	 * @return
	 */
	public static long dayOfWeekStartMillis(int dayOfWeek) {
		Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);

		return dayOfWeekStartMillis(dayOfWeek, calendar);
	}

	private static long dayOfWeekStartMillis(int dayOfWeek, Calendar calendar) {
		int isoDayOfWeek = getIsoDayOfWeek(calendar);
		int days = dayOfWeek - isoDayOfWeek;
		if (days != 0) {
			calendar.add(Calendar.DAY_OF_MONTH, days);
		}

		return calendar.getTimeInMillis();
	}

	/**
	 * 当前时区的本周指定日期的午夜 0 点时刻；
	 * <p>
	 * 单位：毫秒刻度；
	 * 
	 * @param dayOfWeek 表示星期几的数值；按照 ISO 标准，每周从周一开始，1 表示周一，2 表示周二，以此类推，7 表示周日；
	 * @return
	 */
	public static long mondayStartMillisOfDate(int year, int month, int dayOfMonth) {
		Calendar calendar = getCalendar(year, month, dayOfMonth);
		return dayOfWeekStartMillis(1, calendar);
	}

	/**
	 * 当前时区的本月第 1 天的午夜 0 点时刻；
	 * <p>
	 * 单位：毫秒刻度；
	 * 
	 * @return
	 */
	public static long monthStartMillis() {
		Calendar calendar = dayOfCurrentMonthStartMillis(1);
		return calendar.getTimeInMillis();
	}

	/**
	 * 当前时区的本年第 1 天的午夜 0 点时刻；
	 * <p>
	 * 单位：毫秒刻度；
	 * 
	 * @return
	 */
	public static long yearStartMillis() {
		Calendar calendar = dateOfCurrentYearStartMillis(0, 1);
		return calendar.getTimeInMillis();
	}

	/**
	 * 当前时区的本年第 1 天的午夜 0 点时刻；
	 * <p>
	 * 单位：毫秒刻度；
	 * 
	 * @return
	 */
	public static long nextYearStartMillis() {
		Calendar calendar = dateOfCurrentYearStartMillis(0, 1);
		int year = calendar.get(Calendar.YEAR);
		calendar.set(Calendar.YEAR, year + 1);
		return calendar.getTimeInMillis();
	}

	/**
	 * 当前时区的下月第 1 天的午夜 0 点时刻；
	 * <p>
	 * 单位：毫秒刻度；
	 * 
	 * @return
	 */
	public static long nextMonthStartMillis() {
		Calendar calendar = dayOfCurrentMonthStartMillis(1);
		calendar.add(Calendar.MONTH, 1);
		return calendar.getTimeInMillis();
	}

	/**
	 * 本月指定日期的午夜 0 点时刻；
	 * 
	 * @param dayOfMonth 当月的日期，1 表示第 1 天；
	 * @return
	 */
	private static Calendar dayOfCurrentMonthStartMillis(int dayOfMonth) {
		Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
		calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		return calendar;
	}

	/**
	 * 本月指定日期的午夜 0 点时刻；
	 * 
	 * @param month      月份；从 0 开始， 0 表示 1 月；
	 * @param dayOfMonth 指定月份的日期，1 表示第 1 天；
	 * @return
	 */
	private static Calendar dateOfCurrentYearStartMillis(int month, int dayOfMonth) {
		Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
		calendar.set(Calendar.MONTH, month);
		calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
		calendar.set(Calendar.HOUR_OF_DAY, 0);
		calendar.set(Calendar.MINUTE, 0);
		calendar.set(Calendar.SECOND, 0);
		calendar.set(Calendar.MILLISECOND, 0);
		return calendar;
	}

	/**
	 * 当前时区的指定月份第 1 天的午夜 0 点时刻；
	 * <p>
	 * 单位：毫秒刻度；
	 * 
	 * @param dayOfWeek 表示星期几的数值；按照 ISO 标准，每周从周一开始，1 表示周一，2 表示周二，以此类推，7 表示周日；
	 * @return
	 */
	public static long monthStartMillisOfDate(int year, int month) {
		Calendar calendar = getCalendar(year, month, 1);
		return dayOfWeekStartMillis(1, calendar);
	}

	public static String nowString() {
		return nowString(false);
	}

	public static String nowString(boolean millisPrecision) {
		if (millisPrecision) {
			return STANDARD_FULL_FORMAT.format(new Date());
		}
		return STANDARD_FORMAT.format(new Date());
	}

	/**
	 * 以完整的日期时间毫秒刻度格式输出指定的时间字符串；
	 * <p>
	 * 注：以当前系统的默认时区进行时间格式转换；例如，对于时间刻度 0 ，当前系统时区为“东8区”，此时输出： 1970-01-01 08:00:00.000
	 * ；
	 * 
	 * @param time 时间刻度；
	 * @return
	 */
	public static String formatInFull(long time) {
		return STANDARD_FULL_FORMAT.format(new Date(time));
	}

	/**
	 * 返回日历时间；
	 * 
	 * @return
	 */
	public static CalendarDatetime nowDatetime() {
		Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
		CalendarDatetime datetime = createDatetime(calendar);
		return datetime;
	}

	/**
	 * 返回日历时间；
	 * 
	 * @param utcTimeMills UTC 时间刻度；
	 * @return
	 */
	public static CalendarDatetime datetimeOf(long utcTimeMills) {
		Calendar calendar = Calendar.getInstance(TimeZone.getDefault());
		calendar.setTimeInMillis(utcTimeMills);
		CalendarDatetime datetime = createDatetime(calendar);
		return datetime;
	}

	/**
	 * 返回日历时间；
	 * 
	 * @param year       年；
	 * @param month      月；从 0 开始，最大值为 11 ; 0 表示 1 月，11 表示12月；
	 * @param dayOfMonth 日；
	 * @return
	 */
	public static CalendarDatetime datetimeOf(int year, int month, int dayOfMonth) {
		Calendar calendar = getCalendar(year, month, dayOfMonth, 0, 0, 0, 0, TimeZone.getDefault());
		CalendarDatetime datetime = createDatetime(calendar);
		return datetime;
	}

	private static CalendarDatetime createDatetime(Calendar calendar) {
		CalendarDatetime datetime = new CalendarDatetime();
		datetime.year = calendar.get(Calendar.YEAR);
		datetime.month = calendar.get(Calendar.MONTH) + 1;
		datetime.day = calendar.get(Calendar.DAY_OF_MONTH);

		datetime.hour = calendar.get(Calendar.HOUR_OF_DAY);
		datetime.minute = calendar.get(Calendar.MINUTE);
		datetime.second = calendar.get(Calendar.SECOND);

		datetime.millis = calendar.get(Calendar.MILLISECOND);

		int isoDayOfWeek = getIsoDayOfWeek(calendar);
		datetime.dayOfWeek = isoDayOfWeek;
		return datetime;
	}

	private static int getIsoDayOfWeek(Calendar calendar) {
		// 返回值 1 表示周日，2 表示周一，3 表示周二，以此类推， 7 表示周六；
		int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
		// 转换为 ISO 标准表示，1 表示周一，2 表示周二，以此类推， 7 表示周日；
		int isoDayOfWeek = (dayOfWeek + 5) % 7 + 1;
		return isoDayOfWeek;
	}

	/**
	 * 基于日历的日期时间；
	 */
	public static class CalendarDatetime {

		/**
		 * 日历“年”；
		 */
		private int year;

		/**
		 * 日历“月”；
		 * <p>
		 * 从 1 开始；
		 */
		private int month;

		/**
		 * 日历“日”；
		 */
		private int day;

		/**
		 * 小时刻度；
		 * 0 - 23;
		 */
		private int hour;

		/**
		 * 分钟刻度；
		 * 
		 * 0 - 59;
		 */
		private int minute;

		/**
		 * 秒刻度；
		 */
		private int second;

		/**
		 * 毫秒刻度；
		 */
		private int millis;

		/**
		 * 星期几；
		 * <p>
		 * 返回值按照 ISO 标准和中国大陆的习惯，1 表示周一 ，2 表示周二，以此类推... 7 表示周日；
		 */
		private int dayOfWeek;

		/**
		 * 日历“年”；
		 * 
		 * @return the year
		 */
		public int getYear() {
			return year;
		}

		/**
		 * 日历“月”；
		 * <p>
		 * 从 1 开始；
		 * 
		 * @return the month
		 */
		public int getMonth() {
			return month;
		}

		/**
		 * 日历“日”；
		 * 
		 * @return the day
		 */
		public int getDay() {
			return day;
		}

		/**
		 * 小时刻度；
		 * 0 - 23;
		 * 
		 * @return the hour
		 */
		public int getHour() {
			return hour;
		}

		/**
		 * 分钟刻度；
		 * 
		 * 0 - 59;
		 * 
		 * @return the minute
		 */
		public int getMinute() {
			return minute;
		}

		/**
		 * 秒刻度；
		 * 
		 * @return the second
		 */
		public int getSecond() {
			return second;
		}

		/**
		 * 毫秒刻度；
		 * 
		 * @return the millis
		 */
		public int getMillis() {
			return millis;
		}

		/**
		 * @return the dayOfWeek
		 */
		public int getDayOfWeek() {
			return dayOfWeek;
		}

	}
}
